es6中class的深入了解

相信es6中的class(类)对我们来说并不陌生,在我的工作中,也用到了ES6这一特性,虽然一直在使用,但是并没有去认真了解过它,于是做了一些功课,在这里跟大家分享自己的一些心得和看法。

比较

在ES6以前,JS并没有给我们提供对类的支持,常用做法是用构造函数来模拟类的实现,通过将属性和方法定义在原型上将自身的属性共享给它的实例。
来看一个例子:

1
2
3
4
5
6
7
8
9
10
11
function Point(x, y) {
this.x = x;
this.y = y;
}

Point.prototype.toString = function () {
return '(' + this.x + ', ' + this.y + ')';
};

let p = new Point(1, 2);
p.toString()

ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念。通过class关键字,可以定义类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//定义类
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}

toString() {
return `( ${this.x}, ${this.y} )`;
}
}

let p = new Point(1,2)
p.toString()
//(1,2)

解释:
constructor:构造方法,
this: 表示实例对象,
toString: 实例的方法

constructor

constructor是类的默认方法,通过new 命令生成对象实例时,自动调用该方法,一个类必须有constructor方法,如果没有显示定义,会默认添加一个空的constructor方法

1
2
3
4
class Point{ } //等同于
class Point {
constructor () { }
}

constructor方法默认返回实例对象(this),如果我们指定返回另一个对象

1
2
3
4
5
6
7
8
class Person { 
constructor () {
return Object.create(null);
}
}
let p = new Person()
p instanceof Person //false
// 实例 instanceof 构造函数 用来判断实例是否是构造函数的实例

ES6 的(class)类,完全可以看作构造函数的另一种写法, 类的数据类型就是函数,类本身就指向构造函数

1
2
typeof Point // "function"
Point === Point.prototype.constructor // true

类必须使用new调用,否则会报错。
在定义类的时候,前面不需要加function关键字

this 指向

如果类的方法内部含有this,它默认指向类的实例。但是,必须非常小心,一旦单独使用该方法,很可能报错。

1
2
3
4
5
6
7
8
9
10
11
12
13
class Logger {
printName(name = 'there') {
this.print(`Hello ${name}`);
}

print(text) {
console.log(text);
}
}

const logger = new Logger();
const { printName } = logger;
printName(); // TypeError: Cannot read property 'print' of undefined

类的实例对象

与 ES5 一样,实例的属性除非显式定义在其本身(即定义在this对象上),否则都是定义在原型上(即定义在class上)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//定义类
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return `( ${this.x}, ${this.y} )`;
}
}

let point = new Point(2, 3);

point.toString() // (2, 3)

point.hasOwnProperty('x')
point.hasOwnProperty('y')
point.hasOwnProperty('toString')
point.__proto__.hasOwnProperty('toString')

Class 的静态方法

所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。

1
2
3
4
5
6
7
8
class Foo { 
static classMethod() {
return 'hello';
}
}
Foo.classMethod() // 'hello'
let foo = new Foo();
foo.classMethod() // TypeError: foo.classMethod is not a function

思考一下

1
2
3
4
5
6
7
8
9
10
11
12
13
class Foo {
static bar () {
this.baz();
}
static baz () {
console.log('hello');
}
baz () {
console.log('world');
}
}

Foo.bar() // ??

如果静态方法包含this关键字,这个this指的是类,而不是实例, 从这个例子还可以看出,静态方法可以与非静态方法重名。
所以这个例子最终打印出来的会是 hello.

Class 的静态属性和实例属性

静态属性

静态属性和静态方法都是一样,都是定义在类上,而不是实例对象上。
先来感受一下:

1
2
3
4
class Foo {
static prop = 1;
}
Foo.prop // 1

类的实例属性

eg:

1
2
3
4
5
6
7
class MyClass {
myProp = 42;

constructor() {
console.log(this.myProp); // 42
}
}

以前我们定义实例属性,只能写在constructor方法里面,像下面这样

1
2
3
4
5
6
7
8
class Index extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
}

类的继承

Class 可以通过extends关键字实现继承,这比 ES5 的通过修改原型链实现继承,要清晰和方便很多。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 父类
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
static hello() {
console.log('hello world');
}
}

// 子类
class ColorPoint extends Point {
constructor(x, y, color) {
this.color = color; // ReferenceError
super(x, y); // 调用父类的constructor(x, y)
this.color = color; // 正确
}
}

子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类自己的this对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,加上子类自己的实例属性和方法。如果不调用super方法,子类就得不到this对象。
父类的静态方法,也会被子类继承

类的私有属性和私有方法

私有属性

ES6 不支持私有属性。目前,有一个提案,为class加了私有属性。方法是在属性名之前,使用#表示
之所以要引入一个新的前缀#表示私有属性,是因为js没有private关键字
另外,Ruby 语言使用@表示私有属性,ES6 没有用@符号而使用#,是因为@已经被留给了 Decorator

1
2
3
4
5
6
7
8
class Point {
#x;
constructor(x = 0) {
#x = +x; // 写成 this.#x 亦可
}
get x() { return #x }
set x(value) { #x = +value }
}

私有方法

私有方法是常见需求,但 ES6 不提供,只能通过其他方法模拟实现
第一种方法:

1
2
3
4
5
6
7
8
9
10
11
class Widget {
// 公有方法
foo (baz) {
this._bar(baz);
}
// 私有方法
_bar(baz) {
return this.snaf = baz;
}
// ...
}

第二种方法
还有一种方法是利用Symbol值的唯一性,将私有方法的名字命名为一个Symbol值

1
2
3
4
5
6
7
8
9
10
11
12
13
const bar = Symbol('bar');
const snaf = Symbol('snaf');
export default class myClass{
// 公有方法
foo(baz) {
this[bar](baz);
}
// 私有方法
[bar](baz) {
return this[snaf] = baz;
}
// ...
};

感谢支持,我会不断进步